/**
 * @template T
 * @implements RefI<T>
 */
export default class Ref {

  /** @type { T } */
  save = null;
  /** @type { { func: (value: T, oldValue: T) => void; deep?: boolean; }[] } */
  events = [];

  /** @param { T } value */
  constructor(value) {
    this.save = value;
  }

  /** @return { T } */
  get value() {
    return this.save;
  }

  /** @param { T } val */
  set value(val) {
    const oldValue = this.save;
    const isEquals = oldValue === val;
    this.save = val;
    for (const info of this.events) {
      if (!isEquals || info.deep) {
        info.func(val, oldValue);
      }
    }
  }

  /**
   * @param { (newVal: T, oldVal: T) => void } func
   * @param { boolean } [deep]
   */
  bind(func, deep = false) {
    this.events.push({ func, deep });
  }
}

/**
 * @template { RefI<any>[] } T
 * @template { any } R
 * @param { (...refs: RefIValue<T>) => R } func
 * @param { T } refs
*/
Ref.calc = function(func, ...refs) {
  function calc_func() {
    // @ts-ignore
    return func(...refs.map(ref => ref.value));
  }
  const ret = new Ref(calc_func());
  refs.forEach(ref => ref.bind(() => {
    ret.value = calc_func();
  }));
  return ret;
}
